/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package simhya.simengine.hybrid;

import simhya.simengine.odeevents.SwitchingEvent;
import simhya.simengine.odeevents.FixedTimedEvent;
import java.util.ArrayList;
import org.apache.commons.math.ode.events.EventHandler;
import simhya.model.flat.FlatModel;
//import simhya.model.store.Function;
import simhya.model.store.Predicate;
import simhya.model.transition.Transition;
import simhya.simengine.SimulationException;
import java.util.HashMap;
import java.util.TreeSet;
import java.util.HashSet;
import java.util.Collection;

/**
 *
 * @author luca
 */
public class PartitionManager implements DynamicSwitcher {
    ArrayList<SwitchingEvent> localEvents;
    //remember to assign ID to globalevents;
    ArrayList<SwitchingEvent> globalEvents;
    ArrayList<FixedTimedEvent> timedEvents;
    ArrayList<Integer> localAffectedTransitions;
    ArrayList<ArrayList<Integer>> globalAffectedTransitions;
    
    HashMap<Integer,TreeSet<Integer>> dependencyOnVariables;


    HybridOdeFunction function;

    int numberOfTransitions;
    //size equal to #localswitches
    boolean [] localContinuityStatus;
    //size equal to #globalswitches
    boolean [] globalContinuityStatus;
    //size equal to #of transitions
    boolean [] currentStatus;
    
    private int numberOfVariables;
    private HashMap<String,ArrayList<Integer>> transitionNames;

    
    public PartitionManager(FlatModel model, HybridOdeFunction function) {
        this.function = function;
        this.numberOfTransitions = model.getNumberOfTransitions();

        localEvents = new ArrayList<SwitchingEvent>();
        globalEvents = new ArrayList<SwitchingEvent>();
        timedEvents = new ArrayList<FixedTimedEvent>();
        this.dependencyOnVariables = new HashMap<Integer,TreeSet<Integer>>();
        this.globalAffectedTransitions = new ArrayList<ArrayList<Integer>>();
        this.localAffectedTransitions = new ArrayList<Integer>();
        this.localContinuityStatus = new boolean[16];
        this.globalContinuityStatus = new boolean[16];
        this.currentStatus = new boolean[numberOfTransitions];
        this.numberOfVariables = model.getStore().getNumberOfVariables();
        this.transitionNames = new HashMap<String,ArrayList<Integer>>();
        for (Integer j : model.getListOfContinuousTransitionID(true))
            transitionNames.put(model.getTransitionName(j), new ArrayList<Integer>());
        for (Integer j : model.getListOfContinuousTransitionID(true))
            transitionNames.get(model.getTransitionName(j)).add(j);
        this.addLocalSwitchingEvents(model);
    }
    


    private void addLocalSwitchingEvents(FlatModel model) {
        for (Integer i : model.getListOfContinuousTransitionID(true)) {
            Transition t = model.getTransition(i);
            Predicate pf = t.getContinuityStatusPredicate(false);
            Predicate pt = t.getContinuityStatusPredicate(true);
            if (pf != null) { 
                if (!pt.canBeConvertedToFunction())
                    throw new SimulationException("Continuity predicate " + pt.toModelLanguage() + 
                            " cannot be converted to a function! Do not use equality, only inequalities.");
                if (!pf.canBeConvertedToFunction())
                    throw new SimulationException("Continuity predicate " + pf.toModelLanguage() + 
                            " cannot be converted to a function! Do not use equality, only inequalities.");
                SwitchingEvent e = new SwitchingEvent(i,0,pt.convertToFunction(),pf.convertToFunction(),pt,pf,
                        this,true,this.numberOfVariables);
                this.localEvents.add(e);
                this.localAffectedTransitions.add(i);
                //constructs the set of variables on which the event depends from.
                TreeSet<Integer> depVars = new TreeSet<Integer>(); 
                depVars.addAll(pt.getVariableList());
                depVars.addAll(pf.getVariableList());
                this.dependencyOnVariables.put(e.getId(), depVars);
            }
        }
    }
    
  

    public void addGlobalSwitchingRule(Predicate continuousToStochastic,
            Predicate stochasticToContinuous, String... eventsToSwitch) {
        if (continuousToStochastic != null && stochasticToContinuous != null) {
            if (!continuousToStochastic.canBeConvertedToFunction())
                throw new SimulationException("Continuity predicate " + continuousToStochastic.toModelLanguage() + 
                        " cannot be converted to a function! Do not use equality, only inequalities.");
            if (!stochasticToContinuous.canBeConvertedToFunction())
                throw new SimulationException("Continuity predicate " + stochasticToContinuous.toModelLanguage() + 
                        " cannot be converted to a function! Do not use equality, only inequalities.");
            int id = this.globalEvents.size();
            SwitchingEvent e = new SwitchingEvent(id,numberOfTransitions,continuousToStochastic.convertToFunction(),
                    stochasticToContinuous.convertToFunction(),continuousToStochastic,stochasticToContinuous,this,false,this.numberOfVariables);
            ArrayList<Integer> list = new ArrayList<Integer>();
            for (String s : eventsToSwitch) {
                ArrayList<Integer> listToAdd = this.transitionNames.get(s);
                if (listToAdd == null)
                    throw new SimulationException("Event name \"" + s + "\" does not correspond to any model event");
                list.addAll(listToAdd);
            }
            this.globalEvents.add(e);
            this.globalAffectedTransitions.add(list);
            TreeSet<Integer> depVars = new TreeSet<Integer>(); 
            depVars.addAll(continuousToStochastic.getVariableList());
            depVars.addAll(stochasticToContinuous.getVariableList());
            this.dependencyOnVariables.put(e.getId(), depVars);
        }
    }

    public void addGlobalSwitchingRule(Predicate continuousToStochastic,
            Predicate stochasticToContinuous, int... transitionsToSwitch) {
        if (continuousToStochastic != null && stochasticToContinuous != null) {
            if (!continuousToStochastic.canBeConvertedToFunction())
                throw new SimulationException("Continuity predicate " + continuousToStochastic.toModelLanguage() + 
                        " cannot be converted to a function! Do not use equality, only inequalities.");
            if (!stochasticToContinuous.canBeConvertedToFunction())
                throw new SimulationException("Continuity predicate " + stochasticToContinuous.toModelLanguage() + 
                        " cannot be converted to a function! Do not use equality, only inequalities.");
            int id = this.globalEvents.size();
            SwitchingEvent e = new SwitchingEvent(id,numberOfTransitions,continuousToStochastic.convertToFunction(),
                    stochasticToContinuous.convertToFunction(),continuousToStochastic,stochasticToContinuous,
                    this,false,this.numberOfVariables);
            ArrayList<Integer> list = new ArrayList<Integer>();
            for (Integer j : transitionsToSwitch)
                list.add(j);
            this.globalEvents.add(e);
            this.globalAffectedTransitions.add(list);
            TreeSet<Integer> depVars = new TreeSet<Integer>(); 
            depVars.addAll(continuousToStochastic.getVariableList());
            depVars.addAll(stochasticToContinuous.getVariableList());
            this.dependencyOnVariables.put(e.getId(), depVars);
        }
    }
    
    
    /**
     * Returns the list of switching events affected by a collection of variables 
     * @param variables
     * @return 
     */
    public ArrayList<Integer> findEventsAffectedByVariables(Collection<Integer> variables) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        HashSet<Integer> set = new HashSet<Integer>();
        //removing duplicates
        HashSet<Integer> vars = new HashSet<Integer>();
        vars.addAll(variables);
        for (Integer v : vars)
            for (Integer i : this.dependencyOnVariables.keySet())
                if (this.dependencyOnVariables.get(i).contains(v))
                    set.add(i);
        list.addAll(set);
        return list;
    }
    


    /*
     *
     * prendi model e aggiungi un local event per ogni hybrid transition
     * prendi model e global switch conditions e aggiungi global event
     * definisci interfaccia per eventi miei, e ritorna listone con interfaccia
     * in modo da poterli aggiungere in simulator.
     *
     */


    /**
     *  Sets the continuity status of a local switching event
     * @param localSwitchID
     * @param status
     */
    public void setLocalContinuityStatus(int localSwitchID, boolean status) {
        if (this.localContinuityStatus[localSwitchID] != status) {
            localContinuityStatus[localSwitchID] = status;
            this.communicateNewContinuityStatus();
        }
    }

    /**
     * Sets the continuity status of a global switching event
     * @param globalSwitchID
     * @param status
     */
    public void setGlobalContinuityStatus(int globalSwitchID, boolean status) {
        if (this.globalContinuityStatus[globalSwitchID] != status) {
            globalContinuityStatus[globalSwitchID] = status;
            this.communicateNewContinuityStatus();
        }
    }


    private void communicateNewContinuityStatus() {
        //a switchable transition is continuous iff all conditions for it to be continuous are
        //satisfied
        java.util.Arrays.fill(currentStatus, true);
        for (int i=0;i<this.globalContinuityStatus.length;i++)
            for (int j=0;j<this.globalAffectedTransitions.get(i).size();j++)
                currentStatus[globalAffectedTransitions.get(i).get(j)] =
                        currentStatus[globalAffectedTransitions.get(i).get(j)] && globalContinuityStatus[i];
        for (int i=0;i<this.localContinuityStatus.length;i++)
            currentStatus[this.localAffectedTransitions.get(i)] =
                currentStatus[localAffectedTransitions.get(i)] && localContinuityStatus[i];
        function.setContinuityStatus(currentStatus);
    }


    /**
     * 
     * @return the current continuity status of the (hybrid) transitions
     */
    public boolean[] getContinuityStatus() {
        return this.currentStatus;
    }


    /**
     * returns a list of all events defined i the partition manager.
     * @return
     */
    public ArrayList<EventHandler> getEventList() {
        ArrayList<EventHandler> list = new ArrayList<EventHandler>();
        list.addAll(this.globalEvents);
        list.addAll(this.localEvents);
        list.addAll(this.timedEvents);
        return list;
    }
    
    /**
     * returns a list of all switching events defined in the partition manager.
     * @return
     */
    public ArrayList<SwitchingEvent> getSwitchingEventList() {
        ArrayList<SwitchingEvent> list = new ArrayList<SwitchingEvent>();
        list.addAll(this.globalEvents);
        list.addAll(this.localEvents);
        return list;
    }


    
}
